package top.dark.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.transaction.annotation.Transactional;
import top.dark.common.utils.PageUtils;
import top.dark.common.utils.Query;
import top.dark.product.dao.CategoryBrandRelationDao;
import top.dark.product.dao.CategoryDao;
import top.dark.product.entity.CategoryEntity;
import top.dark.product.service.CategoryService;
import top.dark.product.vo.Catelog2Vo;

@Slf4j
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationDao categoryBrandRelationDao;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List listTree() {
        // 将所有的分类信息查询出来
        List<CategoryEntity> entities = baseMapper.selectList(null);

        // 过滤获取根分类
        List<CategoryEntity> root = entities.stream()
                .filter(entity -> entity.getParentCid() == 0)
                .map(entity ->{
                    // 过滤获取子分类（递归）
                    List<CategoryEntity> childs = getChildCategory(entity,entities);
                    entity.setChildren(childs);
                    return entity;
                }).sorted((menu1,menu2)-> Optional.ofNullable(menu1.getSort()).orElse(0) -
                Optional.ofNullable(menu2.getSort()).orElse(0))
                .collect(Collectors.toList());
        return root;
    }

    @Override
    public void removeBatchByIds(List<Long> asList) {
        // TODO  检查当前删除的菜单，是否被别的地方引用
        // 真实业务中多为逻辑删除
        baseMapper.deleteBatchIds(asList);
    }

    @Override
    public Long[] getCatelogPath(Long catelogId) {
        List<Long> catelogPath = new ArrayList<>();
        catelogPath = getParentCatelog(catelogId, catelogPath);
        Collections.reverse(catelogPath);
        return (Long[]) catelogPath.toArray(new Long[catelogPath.size()]);
    }

   /* @Caching(evict = {
            @CacheEvict(value = "category", key = "'getCatelogsFromDb'"),
            @CacheEvict(value = "category", key = "'getLevel1Categorys'")
    })*/
    @CacheEvict(value = "category", allEntries = true)
    @Transactional
    @Override
    public void updateDetail(CategoryEntity category) {
        // 1、更新分类数据
        this.updateById(category);

        // 2、更新品牌关联分类表中的冗余数据，保证数据的一致性
        categoryBrandRelationDao.updateCategoryName(category.getCatId(), category.getName());

        // TODO 更新其余需要保证冗余数据一致性的表

    }

    /**
     * redis中保存复杂业务数据均使用json进行保存
     * 原因：json是跨语言跨平台的语言，能够跨平台实现兼容
     *
     * 产生堆外内存溢出，outofDirectMemoryError
     * 1)、spring boot2.0以后默认使用lettuce作为操作redis的客户端，它使用netty进行网络通信
     * 2）、lettuce的bug导致堆外内存溢出outofDirectMemoryError -Xmx:300m netty如果没有指定内存大小，默认是用-Xmx300
     * 解决方案：不能使用 -Dio.netty.maxDirectMemory 只去调大堆外内存
     * 1、升级lettuce客户端（没有及时的释放连接导致内存溢出，可以通过修改源码解决）  2、使用jedis客户端
     * redisTemplate
     * spring对lettuce和jedis的底层客户端操作，做了再次的封装就是redisTemplate
     */
    // TODO 产生堆外内存溢出，outofDirectMemoryError
    @Override
    public Map<Long, List<Catelog2Vo>> getCatelogJsonFromDbWithRedisLock() {
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if (lock) {
            Map<Long, List<Catelog2Vo>> catelogsFromRedis = null;
            try {
                catelogsFromRedis = getCatelogsFromRedis();
            } finally {
                // redisTemplate.delete("lock");
                // 通过使用lua脚本进行原子性删除
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                Long ok = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
            }
            return catelogsFromRedis;
        }
        log.info("获取分布式锁失败,等待重试.................");
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (Exception e) {
            log.error("休眠异常，异常原因：{}", e);
        }
        // 获取锁失败，自旋在获取（容易堆溢出）
        return getCatelogJsonFromDbWithRedisLock();
    }

    public Map<Long,List<Catelog2Vo>> getCatelogsFromRedis(){
        // 1、缓存逻辑
        Map<Long, List<Catelog2Vo>> result = null;
        String catelogJson = redisTemplate.opsForValue().get("catelogJson");
        if (StringUtils.isBlank(catelogJson)) {
            // 2、缓存中没有，查询数据库
            result = getCatelogsFromDb();
            // 3、查询到的数据在放入到缓存中
            redisTemplate.opsForValue().set("catelogJson", JSON.toJSONString(result));
        } else {
            result = JSON.parseObject(catelogJson,
                    new TypeReference<Map<Long, List<Catelog2Vo>>>() {});
        }
        return result;
    }

    @Cacheable(value = "category",key = "#root.methodName")
    @Override
    public Map<Long,List<Catelog2Vo>> getCatelogsFromDb(){
        // 1、查出所有一级分类
        List<CategoryEntity> level1Category = getLevel1Categorys();

        // 2、整合数据
        Map<Long, List<Catelog2Vo>> categorys = level1Category.stream().collect(Collectors.toMap(k -> k.getCatId(), v -> {
            // 二级菜单
            List<CategoryEntity> level2Categorys = getLevelCategorys(v.getCatId());
            List<Catelog2Vo> catelog2Vos = level2Categorys.stream().map(item -> {
                // 三级菜单
                List<CategoryEntity> level3Categorys = getLevelCategorys(item.getCatId());
                List<Catelog2Vo.Catelog3Vo> level3 = level3Categorys.stream().
                        map(entity -> new Catelog2Vo.Catelog3Vo(entity.getCatId(), item.getCatId(), entity.getName()))
                        .collect(Collectors.toList());
                return new Catelog2Vo(level3, v.getCatId(), item.getCatId(), item.getName());
            }).collect(Collectors.toList());
            return catelog2Vos;
        }));

        return categorys;
    }


    public List<CategoryEntity> getLevelCategorys(Long parentCateId) {
        List<CategoryEntity> categorys = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", parentCateId));
        return categorys;
    }

    @Cacheable(value = "category",key = "#root.method.name")
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        log.info("执行了数据库，categorylevel1...............");
        return getLevelCategorys(0L);
    }

    private List<Long> getParentCatelog(Long catelogId, List<Long> catelogPath) {
        // 1、收集当前节点
        catelogPath.add(catelogId);
        CategoryEntity categoryEntity = baseMapper.selectById(catelogId);
        if (categoryEntity.getParentCid() != 0) {
            // 2、收集父节点
            getParentCatelog(categoryEntity.getParentCid(), catelogPath);
        }
        return catelogPath;
    }

    private List<CategoryEntity> getChildCategory(CategoryEntity entity, List<CategoryEntity> entities) {
         List<CategoryEntity> childs = entities.stream().filter(menu -> menu.getParentCid() == entity.getCatId())
                .map(menu->{
                    menu.setChildren(getChildCategory(menu,entities));
                    return menu;
                })
                .sorted((menu1,menu2)-> Optional.ofNullable(menu1.getSort()).orElse(0)
                        - Optional.ofNullable(menu2.getSort()).orElse(0))
                .collect(Collectors.toList());
        return childs;
    }
}